package service

import (
	"bookkeeping-gin/app/constant"
	"bookkeeping-gin/global"
	"bookkeeping-gin/utils"
	"context"
	"crypto/rsa"
	"errors"
	"fmt"
	"github.com/golang-jwt/jwt/v5"
	"log"
	"strconv"
	"time"
)

type jwtService struct {
}

// JwtUser 所有需要颁发 token 的用户模型必须实现这个接口
type JwtUser interface {
	GetUid() string
	GetUname() string
}

// CustomClaims 自定义 claims
type CustomClaims struct {
	jwt.RegisteredClaims
	CustomField string `json:"custom_field"`
	Username    string `json:"username"`
	Userid      string `json:"userid"`
}

// TokenOutPut token输出
type TokenOutPut struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
	TokenType   string `json:"token_type"`
}

var JwtService = &jwtService{}

const (
	TokenType    = "Bearer"
	AppGuardName = "app"
)

// CreateToken 生成 token
func (j *jwtService) CreateToken(guardName string, user JwtUser) (tokenData TokenOutPut, err error, token *jwt.Token) {
	customClaims := CustomClaims{
		CustomField: "自定义字段",
		Username:    user.GetUname(), // 自定义字段
		Userid:      user.GetUid(),   // 自定义字段
		RegisteredClaims: jwt.RegisteredClaims{
			// A usual scenario is to set the expiration time relative to the current time
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(global.App.Config.Jwt.Expire))), //过期时间
			IssuedAt:  jwt.NewNumericDate(time.Now().Add(time.Second)),                                               //签发时间
			NotBefore: jwt.NewNumericDate(time.Now()),                                                                //最早使用时间
			// guardName 用于在中间件中区分不同客户端颁发的 token，避免 token 跨端使用
			Issuer:   guardName,               // 签发者
			Subject:  user.GetUname(),         // 签发对象
			ID:       user.GetUid(),           // jwt ID, 类似于盐值
			Audience: jwt.ClaimStrings{"App"}, //签发受众
		},
	}
	// 使用 HS256（对称加密） 签名生成 token
	token = jwt.NewWithClaims(jwt.SigningMethodHS256, &customClaims)
	var tokenStr string
	// SignedString 签名
	tokenStr, err = token.SignedString([]byte(global.App.Config.Jwt.Secret))
	tokenData = TokenOutPut{
		tokenStr,
		global.App.Config.Jwt.Expire,
		TokenType,
	}
	return
}

// getBlackListKey 获取黑名单缓存 key
func (j *jwtService) getBlackListKey(tokenStr string) string {
	return constant.JwtBlacklistPrefix + utils.MD5([]byte(tokenStr))
}

// JoinBlackList token 加入黑名单
func (j *jwtService) JoinBlackList(token *jwt.Token) (err error) {
	expirationTime := token.Claims.(*CustomClaims).ExpiresAt.Unix()
	nowUnix := time.Now().Unix()
	timer := time.Duration(expirationTime-nowUnix) * time.Second
	// 将 token 剩余时间设置为缓存有效期，并将当前时间作为缓存 value 值
	err = global.App.Redis.SetNX(context.Background(), j.getBlackListKey(token.Raw), nowUnix, timer).Err()
	return
}

// IsInBlacklist token 是否在黑名单中
func (j *jwtService) IsInBlacklist(tokenStr string) bool {
	var err error
	var joinUnixStr string
	joinUnixStr, err = global.App.Redis.Get(context.Background(), j.getBlackListKey(tokenStr)).Result()
	if joinUnixStr == "" || err != nil {
		log.Println("joinUnixStr err", err)
		return false
	}
	var joinUnix int64
	joinUnix, err = strconv.ParseInt(joinUnixStr, 10, 64)
	if joinUnixStr == "" || err != nil {
		return false
	}
	// BlacklistGracePeriod 为黑名单宽限时间，避免并发请求失效
	if time.Now().Unix()-joinUnix < global.App.Config.Jwt.BlackListGracePeriod {
		return false
	}
	return true
}

// GetUserInfo 根据不同客户端 token ，查询不同用户表数据
func (j *jwtService) GetUserInfo(guardName string, id string) (err error, user JwtUser) {
	switch guardName {
	case AppGuardName:
		return UserService.GetUserInfoById(id)
	default:
		err = errors.New("guard [" + guardName + "] does not exist")
	}
	return
}

/***下面是非对称加密***/

// CreateTokenWithRsa 生成 token
func (j *jwtService) CreateTokenWithRsa(guardName string, user JwtUser) (tokenData TokenOutPut, err error, token *jwt.Token) {
	customClaims := CustomClaims{
		CustomField: "自定义字段",
		Username:    user.GetUname(), // 自定义字段
		Userid:      user.GetUid(),   // 自定义字段
		RegisteredClaims: jwt.RegisteredClaims{
			// A usual scenario is to set the expiration time relative to the current time
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(global.App.Config.Jwt.Expire))),
			IssuedAt:  jwt.NewNumericDate(time.Now().Add(time.Second)),
			NotBefore: jwt.NewNumericDate(time.Now()),
			// 用于在中间件中区分不同客户端颁发的 token，避免 token 跨端使用
			Issuer:   guardName,
			Subject:  user.GetUname(),
			ID:       user.GetUid(),
			Audience: jwt.ClaimStrings{""},
		},
	}
	// 使用 RS256（对称加密） 签名生成 token
	var privateKey *rsa.PrivateKey
	privateKey, err = utils.ParsePrivateKeyBytes([]byte(global.App.Config.Jwt.RsaPrivateKey))
	if err != nil {
		fmt.Println("parsePrivateKeyBytes err", err)
		return
	}
	// 使用 RS256（对称加密） 签名生成 token
	token = jwt.NewWithClaims(jwt.SigningMethodRS256, customClaims)
	var tokenStr string
	// SignedString 签名
	tokenStr, err = token.SignedString(privateKey)
	if err != nil {
		fmt.Println("SignedString err", err)
		return
	}
	tokenData = TokenOutPut{
		tokenStr,
		global.App.Config.Jwt.Expire,
		TokenType,
	}
	return
}

func (j *jwtService) ParseTokenRs256(tokenStr string) (*CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		pub, err := utils.ParsePubKeyBytes([]byte(global.App.Config.Jwt.RsaPublicKey))
		if err != nil {
			fmt.Println("parseTokenRs256 err:", err)
			return nil, err
		}
		return pub, nil
	})
	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, errors.New("claim invalid")
	}

	claims, ok := token.Claims.(*CustomClaims)
	if !ok {
		return nil, errors.New("invalid claim type")
	}

	return claims, nil
}
